package com.wmx.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * AOP 切面拦截 自定义 Redis 分布式锁注解
 * <p>
 * * 1、@Aspect：声明本类为切面类
 * * 2、@Component：将本类交由 Spring 容器管理
 * * 3、@Order：指定切入执行顺序，数值越小，切面执行顺序越靠前，默认为 Integer.MAX_VALUE
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2022/11/20 16:55
 */
@Aspect
@Order(value = 999)
@Component
public class RedisLockAspect {

    private static final Logger LOG = LoggerFactory.getLogger(RedisLockAspect.class);

    /**
     * @Pointcut ：切入点声明，即切入到哪些目标方法。
     * execution：可以用于指定具体类中的具体方法
     * annotation：匹配拥有指定注解的方法; 只匹配实现类中有注解的方法，不会匹配接口中的注解方法; 如果注解是在类上,而不是方法上,并不会匹配类中的全部方法.
     * 用于被下面的通知注解引用，这样通知注解只需要关联此切入点声明即可，无需再重复写切入点表达式
     * @annotation 中的路径表示拦截特定注解
     */
    @Pointcut("@annotation(com.wmx.annotation.RedisLock)")
    public void redisLockPC() {
    }

    /**
     * 环绕通知
     * 1、@Around 的 value 属性：绑定通知的切入点表达式。可以关联切入点声明，也可以直接设置切入点表达式
     * 2、Object ProceedingJoinPoint.proceed(Object[] args) 方法：继续下一个通知或目标方法调用，返回处理结果，如果目标方法发生异常，则 proceed 会抛异常.
     * 3、假如目标方法是控制层接口，则本方法的异常捕获与否都不会影响目标方法的事务回滚
     * 4、假如目标方法是控制层接口，本方法 try-catch 了异常后没有继续往外抛，则全局异常处理 @RestControllerAdvice 中不会再触发
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "redisLockPC()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取注解所在的目标方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        // 获取方法上注解
        RedisLock annotation = method.getAnnotation(RedisLock.class);
        Object[] args = joinPoint.getArgs();
        LOG.info("自定义 Redis 分布式锁注解：方法={} 参数={} 特定参数标识={} 超时重试次数={} 释放时间={} 描述信息={}",
                joinPoint.getSignature(), Arrays.asList(args), annotation.lockFiled(), annotation.retryCount(),
                annotation.lockTime(), annotation.desc());

        // 继续下一个通知或目标方法调用，返回处理结果，如果目标方法发生异常，则 proceed 会抛异常.
        // 如果在调用目标方法或者下一个切面通知前抛出异常，则不会再继续往后走.
        Object proceed = joinPoint.proceed(joinPoint.getArgs());
        return proceed;
    }

}
